package hichecker.ui.components.grading_key_vis;

import hichecker.controller.Config;
import hichecker.university.GradingKey;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Polygon;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.util.HashMap;
import java.util.List;
import java.util.Set;

import javax.swing.JPanel;

public class GradingKeyDiagram extends JPanel {
	private GradingKey gradingKey;
	private HashMap<Float, Integer> countedPoints;
	private int selected = -1;
	private float maxPoints;
	private int numSamePoints;

	private Slider[] sliders;

	private final int BAR_WIDTH = 3;
	//private final int HORIZONTAL_LINES = 5;

	public GradingKeyDiagram(GradingKey gradingKey, List<Float> points, float maxPoints) {
		this.gradingKey = gradingKey;
		this.maxPoints = maxPoints;

		sliders = new Slider[GradingKey.NUM_VALUES];
		for (int i = 0; i < GradingKey.NUM_VALUES; i++)
			sliders[i] = new Slider();

		// count points, get max value of same points
		countedPoints = new HashMap<Float, Integer>();
		numSamePoints = 0;
		for (float point : points) {
			Integer count = countedPoints.get(point);
			count = count == null ? 1 : ++count;
			countedPoints.put(point, count);

			numSamePoints = Math.max(numSamePoints, count);
		}

		addMouseListener(new MouseAdapter() {
			@Override
			public void mousePressed(MouseEvent e) {
				float position = (float) e.getPoint().getX() / getWidth();
				selected = getSelectedThumb(e.getPoint());
				moveSlider(position);
			}

			@Override
			public void mouseReleased(MouseEvent e) {
				selected = -1;
			};
		});
		addMouseMotionListener(new MouseMotionAdapter() {
			@Override
			public void mouseDragged(MouseEvent e) {
				float position = (float) e.getPoint().getX() / getWidth();
				moveSlider(position);
			}
		});
	}

	@Override
	public void paint(Graphics g) {
		super.paint(g);
		Graphics2D g2d = (Graphics2D) g;

		//// dimension
		int width = getWidth() - 2 * BAR_WIDTH;
		int height = getHeight();


		//// paint num grades bars
		int[] grades = gradingKey.calcGrades(countedPoints, maxPoints);

		// get max number of same grades
		int numSameGrade = 0;
		for (int grade : grades)
			numSameGrade = Math.max(numSameGrade, grade);

		// get max number of same grades and same points (+1 for space upwards)
		int maxValue = Math.max(numSameGrade, numSamePoints) + 1;

		// set grid
		float gridX = width / GradingKey.NUM_VALUES + 1;
		float gridY = (float) height / (maxValue);

		// color
		g2d.setColor(Config.COLOR_CHANGED_UNSELECTED);

		// paint bars
		int left = 0, right = 0;
		for (int i = 0; i < grades.length; i++) {
			// left border is old right border
			left = right;
			// right border is next border or max width
			if (i < GradingKey.NUM_VALUES) {
				float p = GradingKey.convertToGrid(gradingKey.getPercentage(i) * maxPoints);
				right = (int) (p / maxPoints * width);
			} else {
				right = width;
			}
			// bottom border
			int bottom = (int) (gridY * (maxValue - grades[i]));

			// draw rectangle
			g.fillRect(left, bottom, right - left, height - bottom);
		}

		//// paint horizontal lines
		g2d.setColor(Color.GRAY);
		for (float i = 0; i < maxValue; i++) {
			int y = (int) (i * gridY);
			g2d.drawLine(0, y, width, y);
		}

		//// paint num points bars
		// set new horizontal grid
		gridX = width / maxPoints;

		// color
		g2d.setColor(new Color(0, 115, 187));

		// paint bars
		Set<Float> points = countedPoints.keySet();
		for (float point : points) {
			// left and bottom border
			int count = countedPoints.get(point);
			left = (int) (gridX * point);
			int bottom = (int) ((gridY * (maxValue - count)));

			// draw rectangle
			g.fillRect(left, bottom, BAR_WIDTH * 2, height - bottom);
		}


		//// paint borders
		// set line style
		final BasicStroke dashed = new BasicStroke(1.0f, BasicStroke.CAP_BUTT,
				BasicStroke.JOIN_MITER, 3.0f, new float[] { 3.0f }, 0.0f);
		g2d.setStroke(dashed);
		g2d.setColor(Color.BLACK);

		// paint borders
		for (float k : gradingKey.getKey()) {
			// line position
			float p = GradingKey.convertToGrid(k * maxPoints);
			int x = (int) (p / maxPoints * width);

			// paint line
			g2d.drawLine(x, 0, x, height);
		}

		//// paint slider
		int offset = 0;
		float prevPos = -1;
		for (int i = 0; i < GradingKey.NUM_VALUES; i++) {
			// set floating color
			int red = 202 + (int) (43d / (GradingKey.NUM_VALUES - 1) * i);
			int blue = 34 + (int) (150d / (GradingKey.NUM_VALUES - 1) * i);
			int green = 27 - (int) (25d / (GradingKey.NUM_VALUES - 1) * i);
			g.setColor(new Color(red, blue, green));

			// get slider position
			float p = GradingKey.convertToGrid(gradingKey.getPercentage(i) * maxPoints);
			// arrange slider at same position vertical
			offset = prevPos == p ? offset + 10 : 0;
			prevPos = p;

			// draw slider
			int x = (int) (p / maxPoints * width);
			int y = offset;
			sliders[i].setPosition(x, y);
			g.fillPolygon(sliders[i]);
		}
	}

	/**
	 * 
	 * @param value 0..1
	 */
	private void moveSlider(float value) {
		if (selected == -1 || value < 0 || value > 1)
			return;

		// Wert setzen, andere Slider evtl. verschieben um Reihenfolge zu erhalten
		gradingKey.setPercentage(selected, value);

		for (int i = 0; i < selected; i++)
			gradingKey.setPercentage(i, Math.min(gradingKey.getPercentage(i), gradingKey.getPercentage(selected)));
		for (int i = selected + 1; i < GradingKey.NUM_VALUES; i++)
			gradingKey.setPercentage(i, Math.max(gradingKey.getPercentage(i), gradingKey.getPercentage(selected)));

		repaint();
	}

	/**
	 * 
	 * @param value 0..1
	 * @return
	 */
	private int getSelectedThumb(Point p) {
		for (int i = 0; i < GradingKey.NUM_VALUES; i++)
			if (sliders[i].contains(p))
				return i;
		return -1;
	}

	/**
	 * Representation of a single slider.
	 *
	 */
	private class Slider extends Polygon {
		public void setPosition(int x, int y) {
			npoints = 4;
			xpoints = new int[] { x - 4, x + 4, x + 4, x - 4 };
			ypoints = new int[] { y, y, y + 8, y + 8 };
		}

		@Override
		public boolean contains(Point p) {
			// TODO unnecessary, find error
			Polygon pol = new Polygon(xpoints, ypoints, npoints);
			return pol.contains(p);
		}
	}
}
